{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "basic_text_classification.ipynb",
      "version": "0.3.2",
      "provenance": [],
      "private_outputs": true,
      "collapsed_sections": [],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Ic4_occAAiAT"
      },
      "source": [
        "##### Copyright 2018 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "cellView": "form",
        "colab_type": "code",
        "id": "ioaprt5q5US7",
        "colab": {}
      },
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "cellView": "form",
        "colab_type": "code",
        "id": "yCl0eTNH5RS3",
        "colab": {}
      },
      "source": [
        "#@title MIT License\n",
        "#\n",
        "# Copyright (c) 2017 François Chollet\n",
        "#\n",
        "# Permission is hereby granted, free of charge, to any person obtaining a\n",
        "# copy of this software and associated documentation files (the \"Software\"),\n",
        "# to deal in the Software without restriction, including without limitation\n",
        "# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n",
        "# and/or sell copies of the Software, and to permit persons to whom the\n",
        "# Software is furnished to do so, subject to the following conditions:\n",
        "#\n",
        "# The above copyright notice and this permission notice shall be included in\n",
        "# all copies or substantial portions of the Software.\n",
        "#\n",
        "# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n",
        "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n",
        "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n",
        "# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n",
        "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n",
        "# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n",
        "# DEALINGS IN THE SOFTWARE."
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ItXfxkxvosLH"
      },
      "source": [
        "# Классификация текста обзоров фильмов"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "hKY4XMc9o8iB"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/ru/r1/tutorials/keras/basic_text_classification.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Запусти в Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/ru/r1/tutorials/keras/basic_text_classification.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />Изучай код на GitHub</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "R80081eTd2XD"
      },
      "source": [
        "Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует [официальной документации на английском языке](https://www.tensorflow.org/?hl=en). Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в [tensorflow/docs](https://github.com/tensorflow/docs) репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на [docs-ru@tensorflow.org list](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ru)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Eg62Pmz3o83v"
      },
      "source": [
        "\n",
        "В этом интерактивном уроке мы построим модель, которая будет классифицировать обзор фильма как *позитивный* или *негативный* на основе текста. Это пример *бинарной* классификации (по двум классам), важной, и широко применяющейся задачи машинного обучения.\n",
        "\n",
        "Мы воспользуемся [датасетом IMDB](https://www.tensorflow.org/api_docs/python/tf/keras/datasets/imdb), который содержит тексты 50,000 обзоров фильмов из [Internet Movie Database](https://www.imdb.com/). Они разделены на 25,000 обзоров для обучения, и 25,000 для проверки модели. Тренировочные и проверочные датасеты *сбалансированы*, т.е. содержат одинаковое количество позитивных и негативных обзоров.\n",
        "\n",
        "Данное руководство использует [tf.keras](https://www.tensorflow.org/r1/guide/keras), высокоуровневый API для создания и обучения моделей в TensorFlow. Чтобы сделать более сложную по структуре классификацую текста при помощи `tf.keras`, читай [Руководство по классификации текстов](https://developers.google.com/machine-learning/guides/text-classification/)."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "4v3p_h3LBekM",
        "colab": {}
      },
      "source": [
        "# keras.datasets.imdb is broken in 1.13 and 1.14, by np 1.16.3\n",
        "!pip install tf_nightly"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "2ew7HTbPpCJH",
        "colab": {}
      },
      "source": [
        "from __future__ import absolute_import, division, print_function, unicode_literals, unicode_literals\n",
        "\n",
        "import tensorflow as tf\n",
        "from tensorflow import keras\n",
        "\n",
        "import numpy as np\n",
        "\n",
        "print(tf.__version__)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "iAsKG535pHep"
      },
      "source": [
        "## Загружаем датасет IMDB\n",
        "\n",
        "Датасет IMDB доступен сразу в TensorFlow при помощи метода `load_data`. Он уже подготовлен таким образом, что обзоры (последовательности слов) были конвертированы в последовательность целых чисел, где каждое целое представляет конкретное слово в массиве.\n",
        "\n",
        "Давай напишем пару строчек кода чтобы загрузить датасет (или автоматически используем копию из кэша, если ты уже скачал этот набор данных):"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "zXXx5Oc3pOmN",
        "colab": {}
      },
      "source": [
        "imdb = keras.datasets.imdb\n",
        "\n",
        "(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "odr-KlzO-lkL"
      },
      "source": [
        "Аргумент `num_words=10000` позволяет нам ограничиться только 10,000 наиболее часто встречающимися словами из тренировочного сета. Все редкие слова исключаются. Это поможет нам держать объем данных в разумных пределах."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "l50X3GfjpU4r"
      },
      "source": [
        "## Знакомимся с данными\n",
        "\n",
        "Давай посмотрим какая информация нам доступна. Данные уже подготовлены: каждый пример - это массив целых чисел, которые представляют слова из обзоров. Каждая метка *label* является целым числом 0 или 1:\n",
        "\n",
        "0 - негативный обзор, 1 - позитивный."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "y8qCnve_-lkO",
        "colab": {}
      },
      "source": [
        "print(\"Тренировочных записей: {}, меток: {}\".format(len(train_data), len(train_labels)))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "RnKvHWW4-lkW"
      },
      "source": [
        "Текст обзоров уже был конвертирован в целые числа, где каждое целок представляет слово из словаря. Вот пример того, как выглядит первый обзор:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "QtTS4kpEpjbi",
        "colab": {}
      },
      "source": [
        "print(train_data[0])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "hIE4l_72x7DP"
      },
      "source": [
        "Разные обзоры фильмов также имеют разное количество слов. Код ниже поможет нам узнать количество слов в первом и втором обзоре. Поскольку нейросеть может принимать только данные одинаковой длины, то нам предстоит как-то решить эту задачу."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "X-6Ii9Pfx6Nr",
        "colab": {}
      },
      "source": [
        "len(train_data[0]), len(train_data[1])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "4wJg2FiYpuoX"
      },
      "source": [
        "### Конвертируем целые обратно в слова\n",
        "\n",
        "Не будет лишним также знать, как конвертировать целые числа из массива обратно в текст. Напишем вспомогательную функцию, с помощью который мы сможем запрашивать из этого словаря объект, который содержит указанные числа и отображать их в виде слов:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "tr5s_1alpzop",
        "colab": {}
      },
      "source": [
        "# Назначим словарь, который будет отображать слова из массива данных\n",
        "word_index = imdb.get_word_index()\n",
        "\n",
        "# Зарезервируем первые несколько значений\n",
        "word_index = {k:(v+3) for k,v in word_index.items()}\n",
        "word_index[\"<PAD>\"] = 0\n",
        "word_index[\"<START>\"] = 1\n",
        "word_index[\"<UNK>\"] = 2  # Вместо редких слов, не вошедших в набор из 10,000, будет указано UNK\n",
        "word_index[\"<UNUSED>\"] = 3\n",
        "\n",
        "reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])\n",
        "\n",
        "def decode_review(text):\n",
        "    return ' '.join([reverse_word_index.get(i, '?') for i in text])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "U3CNRvEZVppl"
      },
      "source": [
        "Теперь мы можем легко воспользоваться функцией `decode_review` для отображения текста первого обзора фильма:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "s_OqxmH6-lkn",
        "colab": {}
      },
      "source": [
        "decode_review(train_data[0])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "lFP_XKVRp4_S"
      },
      "source": [
        "## Подготавливаем данные\n",
        "\n",
        "Обзоры фильмов из массива целых чисел должны быть конвертированы в тензоры прежде, чем они будут пропущены через нейросеть. Эта конвертация может быть сделана несколькими способами:\n",
        "\n",
        "* *One-hot encoding* конвертирует массивы в векторы 0 и 1. Например, последовательность [3, 5] станет 10,000-мерным вектором, полностью состоящим из нулей кроме показателей 3 и 5, которые будут представлены единицами. Затем, нам нужно будет создать первый `Dense` слой в нашей сети, который сможет принимать векторые данные с плавающей запятой. Такой подход очень требователен к объему памяти, несмотря на то, что требует указать размеры матрицы `num_words * num_reviews`\n",
        "\n",
        "* Другой способ - сделать все массивы одинаковыми по длине, а затем создать тензор целых чисел с указанием `max_length * num_reviews`. Мы можем использовать *Embedding* (пер. \"Встроенный\") слой, который может использовать эти параметры в качестве первого слоя нашей сети\n",
        "\n",
        "В этом руководстве мы попробуем второй способ.\n",
        "\n",
        "Поскольку все обзоры фильмов должны быть одинаковой длины, то мы используем функцию [pad_sequences](https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/sequence/pad_sequences), чтобы привести все длины к одному значению:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "2jQv-omsHurp",
        "colab": {}
      },
      "source": [
        "train_data = keras.preprocessing.sequence.pad_sequences(train_data,\n",
        "                                                        value=word_index[\"<PAD>\"],\n",
        "                                                        padding='post',\n",
        "                                                        maxlen=256)\n",
        "\n",
        "test_data = keras.preprocessing.sequence.pad_sequences(test_data,\n",
        "                                                       value=word_index[\"<PAD>\"],\n",
        "                                                       padding='post',\n",
        "                                                       maxlen=256)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "VO5MBpyQdipD"
      },
      "source": [
        "Давай теперь посмотрим на длину наших примеров:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "USSSBnkE-lky",
        "colab": {}
      },
      "source": [
        "len(train_data[0]), len(train_data[1])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "QJoxZGyfjT5V"
      },
      "source": [
        "А также проверим как выглядит первый стандартизированный по длине обзор фильма:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "TG8X9cqi-lk9",
        "colab": {}
      },
      "source": [
        "print(train_data[0])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "LLC02j2g-llC"
      },
      "source": [
        "## Строим модель\n",
        "\n",
        "Нейронная сеть создается посредством стека (наложения) слоев - это требует ответов на два вопроса об архитектуре самой модели:\n",
        "\n",
        "* Сколько слоев будет использовано в модели?\n",
        "* Сколько *скрытых блоков* будет использовано для каждого слоя?\n",
        "\n",
        "В этом примере, входные данные состоят из массива слов (целых чисел). Получаемые предсказания будут в виде меток 0 или 1. Давай построим модель, которая будет решать нашу задачу:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "xpKOoWgu-llD",
        "colab": {}
      },
      "source": [
        "# Размер входных данных - количество слов, использованных в обзорах фильмов (10,000 слов)\n",
        "vocab_size = 10000\n",
        "\n",
        "model = keras.Sequential()\n",
        "model.add(keras.layers.Embedding(vocab_size, 16, input_shape=(None,)))\n",
        "model.add(keras.layers.GlobalAveragePooling1D())\n",
        "model.add(keras.layers.Dense(16, activation=tf.nn.relu))\n",
        "model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))\n",
        "\n",
        "model.summary()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "6PbKQ6mucuKL"
      },
      "source": [
        "Для создания классификатора все слои проходят процесс стека, или наложения:\n",
        "\n",
        "1. Первый `Embedding` слой принимает переведенные в целые числа слова и ищет соответствующий вектор для каждой пары слово/число. Модель обучается на этих векторах. Векторы увеличивают размер получаемого массива на 1, в результате чего мы получаем измерения: `(batch, sequence, embedding)`\n",
        "\n",
        "2. Следующий слой `GlobalAveragePooling1D` возвращает получаемый вектор заданной длины для каждого примера, усредняя размер ряда. Это позволит модели легко принимать данные разной длины\n",
        "\n",
        "3. Этот вектор пропускается через полносвязный `Dense` слой с 16 скрытыми блоками\n",
        "\n",
        "4. Последний слой также является полносвязным, но с всего одним выходящим нодом. При помощи функции активации `sigmoid` (Сигмоида) мы будем получать число с плавающей запятой между 0 и 1, которое будет показывать вероятность или уверенность модели"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "0XMwnDOp-llH"
      },
      "source": [
        "### Скрытые блоки\n",
        "\n",
        "Вышеописанная модель имеет 2 промежуточных или *скрытых* слоя, между входом и выходом данных. Количество выходов (блоков, нодов или нейронов) является размером репрезентативного пространства слоя. Другими словами, количество свободы, которая разрешена сети во время обучения.\n",
        "\n",
        "Если модель имеет больше скрытых блоков, и/или больше слоев, то тогда нейросеть может обучиться более сложным представлениям. Однако в этом случае это будет дороже с точки зрения вычислительных ресурсов и может привести к обучению нежелательных паттернов - паттернов, которые улучшают показатели на тренировочных данных, но не на проверочных. Это называется *переобучением*, и мы обязательно познакомимся с этим явлением далее."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "L4EqVWg4-llM"
      },
      "source": [
        "### Функция потерь и оптимизатор\n",
        "\n",
        "Для модели нам необходимо указать функцию потерь и оптимизатор для обучения. Поскольку наша задача является примером бинарной классификации и модель будет показывать вероятность (слой из единственного блока с сигмоидой в качестве функции активации), то мы воспользуемся функцией потерь `binary_crossentropy` (пер. \"Перекрестная энтропия\").\n",
        "\n",
        "Это не единственный выбор для нашей функции потерь: ты можешь, например, выбрать `mean_squared_error`. Но обычно `binary_crossentropy` лучше справляется с вероятностями - она измеряет \"дистанцию\" между распределениями вероятностей, или, как в нашем случае, между эталоном и предсказаниями.\n",
        "\n",
        "Далее, по мере знакомства с задачами регрессии (например, предсказание цен на недвижимость), мы посмотрим как использовать другую функцию потерь, которая называется среднеквадратичская ошибка (MSE).\n",
        "\n",
        "А сейчас, настроим нашу модель: мы будем использовать *оптимизатор Адама* и *перекрестную энтропию* для потерь:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "Mr0GP-cQ-llN",
        "colab": {}
      },
      "source": [
        "model.compile(optimizer=tf.train.AdamOptimizer(),\n",
        "              loss='binary_crossentropy',\n",
        "              metrics=['accuracy'])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "hCWYwkug-llQ"
      },
      "source": [
        "## Создадим проверочный набор данных\n",
        "\n",
        "Во время обучения мы хотим проверить точность нашей модели на данных, которых она еще не видела. Давай создадим *проверочный сет* данных, выделив 10,000 примеров из оригинального тренировочного сета в отдельный набор.\n",
        "\n",
        "Почему мы не используем проверочный набор прямо сейчас? Наша цель - разработать и настроить нашу модель, используя только данные для обучения, и только потом использовать проверочный сет всего один раз чтобы оценить точность."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "-NpcXY9--llS",
        "colab": {}
      },
      "source": [
        "x_val = train_data[:10000]\n",
        "partial_x_train = train_data[10000:]\n",
        "\n",
        "y_val = train_labels[:10000]\n",
        "partial_y_train = train_labels[10000:]"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "35jv_fzP-llU"
      },
      "source": [
        "## Обучаем модель\n",
        "\n",
        "Начнем тренировку нашей модели с 40 эпох при помощи мини-батчей по 512 образцов (*батч* - набор, пакет данных). Это означает, что мы сделаем 40 итераций (или проходов) по всем образцам данных в тензорах `x_train` и `y_train`. После обучения мы узнаем потери и точность нашей модели, показав ей 10,000 образцов из проверочного набора данных:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "tXSGrjWZ-llW",
        "colab": {}
      },
      "source": [
        "history = model.fit(partial_x_train,\n",
        "                    partial_y_train,\n",
        "                    epochs=40,\n",
        "                    batch_size=512,\n",
        "                    validation_data=(x_val, y_val),\n",
        "                    verbose=1)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "9EEGuDVuzb5r"
      },
      "source": [
        "## Оценим точность модели\n",
        "\n",
        "Теперь когда обучение прошло успешно, давай посмотрим какие результаты показывает модель.\n",
        "\n",
        "Она будет возвращать 2 значения: потери *loss* (число, которое показывает ошибку, чем оно ниже, тем лучше), и точность *accuracy*."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "zOMKywn4zReN",
        "colab": {}
      },
      "source": [
        "results = model.evaluate(test_data,  test_labels, verbose=2)\n",
        "\n",
        "print(results)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "z1iEXVTR0Z2t"
      },
      "source": [
        "Как мы видим, этот достаточно наивный подход достиг точности около 87%. Если бы мы использовали более сложные методы, то модель приблизилась бы к отметке в 95%."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "5KggXVeL-llZ"
      },
      "source": [
        "## Построим временной график точности и потерь\n",
        "\n",
        "Метод `model.fit()` возвращает объект `History`, который содержит все показатели, которые были записаны в лог во время обучения:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "VcvSXvhp-llb",
        "colab": {}
      },
      "source": [
        "history_dict = history.history\n",
        "history_dict.keys()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "nRKsqL40-lle"
      },
      "source": [
        "Здесь всего четыре показателя, по одному для каждой отслеживаемой метрики во время обучения и проверки. Мы можем использовать их, чтобы построить графики потерь и точности обоих стадий для сравнения:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "nGoYf2Js-lle",
        "colab": {}
      },
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "acc = history.history['acc']\n",
        "val_acc = history.history['val_acc']\n",
        "loss = history.history['loss']\n",
        "val_loss = history.history['val_loss']\n",
        "\n",
        "epochs = range(1, len(acc) + 1)\n",
        "\n",
        "# \"bo\" означает \"blue dot\", синяя точка\n",
        "plt.plot(epochs, loss, 'bo', label='Потери обучения')\n",
        "# \"b\" означает \"solid blue line\", непрерывная синяя линия\n",
        "plt.plot(epochs, val_loss, 'b', label='Потери проверки')\n",
        "plt.title('Потери во время обучения и проверки')\n",
        "plt.xlabel('Эпохи')\n",
        "plt.ylabel('Потери')\n",
        "plt.legend()\n",
        "\n",
        "plt.show()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "6hXx-xOv-llh",
        "colab": {}
      },
      "source": [
        "plt.clf()   # Очистим график\n",
        "acc_values = history_dict['acc']\n",
        "val_acc_values = history_dict['val_acc']\n",
        "\n",
        "plt.plot(epochs, acc, 'bo', label='Точность обучения')\n",
        "plt.plot(epochs, val_acc, 'b', label='Точность проверки')\n",
        "plt.title('Точность во время обучения и проверки')\n",
        "plt.xlabel('Эпохи')\n",
        "plt.ylabel('Точность')\n",
        "plt.legend()\n",
        "\n",
        "plt.show()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "oFEmZ5zq-llk"
      },
      "source": [
        "\n",
        "На графиках точками отмечены потери и точность модели во время обучения, а линией - во время проверки.\n",
        "\n",
        "Обрати внимание что потери во время обучения *уменьшаются*, а точность - *увеличивается* с каждой следующей эпохой. Это вполне ожидаемо, поскольку мы используем градиентный спуск *gradient descent* - он минимизирует показатели потерь с каждой итерацией настолько быстро, насколько это возможно.\n",
        "\n",
        "Но это совсем не тот случай если мы посмотрим на потери и точность во время проверки модели: после приблизительно 20 эпох они находятся на пике. Это явный пример переобучения: модель показывает более лучшие показатели на данных для обучения, нежели на новых, которых она еще не видела. После этого момента модель начинает переоптимизироваться и обучается представлениям, которые *являются свойственны* только данным для обучения. Таким образом, модель не учится *обобщать* новые, проверочные данные.\n",
        "\n",
        "Именно здесь мы можем предотвратить переобучение просто прекратив тренировку сразу после 20 эпох обучения. Далее мы посмотрим, как это можно сделать автоматически при помощи *callback*, функции обратного вызова."
      ]
    }
  ]
}